<?php

/**
 * @desc ActiveRecord pattern. Automatically build load/save requests. 
 *
 */
abstract class Kernel_Action_Form_Simple extends Kernel_Action_Form 
{
    
    /**
    * @desc If this param is FALSE then try to add table postfix. Change it to 
    * TRUE if there's no need to add language postfix.
    * 
    * @param boolean
    */
    public $no_postfix = false;

    /**
     * Основная таблица, в которой хранится редактируемый элемент.
     *
     * @var string
     */
    protected $_table;

    /**
     * Элементы основной таблицы, которые нужно включить в форму.
     *
     * @var array
     */
    protected $_include = array();

    /**
     * Элементы основной таблицы, которые нужно исключить из формы.
     *
     * @var array
     */
    protected $_exclude = array();
    
    /**
    * Unique column name. Used to find duplicate entry.
    * 
    * @var string
    */
    protected $_unique_column;

    /**
     * Наименования полей, которые нужно сохранить в основную таблицу -
     * используется для отсеивания полей, добавленных вручную при формировании
     * запросов на запись данных.
     *
     * @var array
     */
    private $_table_fields = array();

    /**
     * Проекция кодов исключения БД на сообщения об ошибке сохранения формы.
     *
     * @var array
     */
    private $_map = array();
    
    /**
    * Name of the position element.
    * 
    * @var string
    */
    private $_pos_element; 
    
    
    /**
     * Control/prepare params.
     *
     */
    public function __construct($params = array()) 
    {
        parent::__construct($params);
        
        $this->_setPositionElement();
    }

    /**
     * Реализация обработки пользовательского запроса.
     *
     */
    protected function _process() 
    {
        if (is_null($this->_table)) {
            trigger_error('Form table is not specified', E_USER_ERROR);
        }
        parent::_process();
    }

    /**
     * Initialize form elements. 
     *
     */
    protected function _initElements() 
    {
        // if table is defined then add prefix if needed
        if (!is_null($this->_table) && !$this->no_postfix) {
            $this->_table = __postfix($this->_table);
        }
        
        if (!empty($this->_include) && !empty($this->_exclude)) {
            trigger_error('Please specify either $_include or $_exclude, but not both', E_USER_ERROR);
        } else {
            $this->_table_fields = !empty($this->_include)
                ? $this->_fromTable($this->_table, $this->_include, self::INCLUSIVE)
                : $this->_fromTable($this->_table, $this->_exclude, self::EXCLUSIVE);
        }
    }

    /**
     * Load form data.
     *
     * @return  array
     */
    protected function _load() 
    {
        static $data = array();
        
        if (empty($data)) {
            $fields = array();
            foreach ($this->_table_fields as $field) {
                if ('file' != $this->_getElementParam($field, 'type')) {
                    $fields[] = '`' . $field . '`';
                }
            }
            $query = 'SELECT
                             ' . implode(', ', $fields) . '
                        FROM
                             `' . $this->_table . '`
                       WHERE
                             `' . $this->_id_field . '` = "' . $this->id . '"';
            $data = database::fetchRow($query);
        }
        
        return $data;
    }

    /**
     * @desc Save form data.
     *
     * @throws  Kernel_Form_Exception_Save
     */
    protected function _save() 
    {
        try {
            parent::_save();
        } catch (Kernel_Db_Exception $e) {
            $this->_exceptionCallback();
            throw new Kernel_Form_Exception_Save($this->_message($e));
        }
    }

    /**
     * Add new record.
     *
     */
    protected function _insert() 
    {
        // in case of position element add it together with the rest data
        if ($this->_pos_element) {
            $data = array_extract($this->_data(), 
                // extract from data all but position element
                array_diff(array_keys($this->_data()), array($this->_pos_element)));
            $params = array('table' => $this->_table);
            $this->id = Kernel::factory('Kernel_Form', 'Order', $params)
                ->addNode($this->_getValue('position'), $data);
        } else {
            $this->id = database::insert($this->_table, $this->_data());
        }
    }

    /**
     * Update current record.
     *
     */
    protected function _update() 
    {
        if ($this->_pos_element) {
            Kernel::factory('Kernel_Form', 'Order', array('table' => $this->_table))
                ->moveNode($this->id, $this->_getValue($this->_pos_element));
        }
        
        database::update($this->_table, $this->_data(), array($this->_id_field => $this->id));
    }

    /**
     * Добавляет сообщение об ошибке сохранения формы для указанного кода.
     *
     * @param   integer $code
     * @param   string  $message
     */
    protected function _map($code, $message = null) 
    {
        foreach (is_array($code) ? $code : array($code => $message) as $code => $message) {
            $this->_map[$code] = $message;
        }
        return $this;
    }

    /**
     * Формирует сообщение об ошибке сохранения формы на основе указанного
     * исключения.
     *
     * @param   Kernel_Db_Exception $e
     * @return  string
     */
    protected function _message(Kernel_Db_Exception $e) 
    {
        $code = $e->getCode();
        return isset($this->_map[$code]) ? $this->_map[$code] : $e->getMessage();
    }

    /**
     * Возвращает данные для сохранения — только те, которые нужно сохранить в
     * основной таблице.
     *
     * @return  array
     */
    protected function _data() 
    {
        return array_extract(parent::_data(),

            // пропускаем незагруженные файлы — это разница
            array_diff($this->_table_fields, array_diff(

                // наименования всех полей-файлов
                array_keys($this->_getElementsByFilter(array('type' => 'file'))), 
                

                // и наименования полей успешно загруженных файлов
                array_keys($this->_getFiles()))));
    }
    
    /**
    * Now used to build link for duplicate entry.
    * 
    */
    protected function _exceptionCallback() 
    {
        if (is_null($this->_unique_column)) {
            $query = '
                SHOW INDEX 
                      FROM 
                           `' . $this->_table . '` 
                     WHERE 
                           `Key_name` != "PRIMARY" 
                       AND 
                           `Non_unique` = 0';
            $meta_data = database::fetchRow($query);
            $this->_unique_column = $meta_data['Column_name'];
        }
        
        $query = '
            SELECT 
                   `' . $this->_id_field . '` 
              FROM 
                   `' . $this->_table . '` 
             WHERE 
                   `' . $this->_unique_column . '` = 
                   "' . database::escape($this->_getValue($this->_unique_column)) . '"
            ';
        
        return $link = Kernel_Manager_List::managerUrl(
            array('action' => 'edit', 'id' => database::fetchOne($query)));
    }
    
    /**
    * Set positions in the list
    * 
    */
    protected function _setPositionElement() 
    {
        $elements = $this->_getElementsByFilter(array('type' => 'position'));
        if (empty($elements)) { return; }
        
        // on this moment it means that position element is the only one
        $this->_pos_element = key($elements);
        
        $position = new Kernel_Form_Value_Position($this->_table, $this->_isNew());
        $this->_setElementParam($this->_pos_element, 'values', $position->values());
        
        if ($this->_isNew()) {
            $this->_setElementParam($this->_pos_element, 'value', count($position->values()));
        }
    }
    
}
